/*
 * Institution.java
 *
 *  
 */
package org.msb.finance.data;

import java.util.*;

/**
 * The {@code Institution} class stores information about an institution where a financial account can be held. All
 * fields except for {@code name} are optional.
 * 
 * <h3>Name uniqueness</h3>
 * The {@code name} field must be unique among all {@code Institution} instances. Setting the value of the {@code name}
 * field to a name already used will result in a {@link DuplicateNameException} being thrown. Also, an
 * {@code Institution} object cannot be added to the {@link Cabinet} object unless it has been assigned a name.
 * 
 * <h3>Relationships</h3>
 * This class maintains a many-to-one relationship with {@link Account} instances. The related {@link Account} objects
 * can be queried from instances of this class, but the relationship can only be established/broken from the
 * {@link Account} class via the {@link Account#setInstitution} method.
 * 
 * @author Marc Boudreau
 * 
 */
public class Institution implements Comparable<Institution> {

	/*
	 * The name of the institution that will be used by the user interface to refer to this instance.
	 */
	private String name;

	/*
	 * The address of the institution.
	 */
	private String address;

	/*
	 * The telephone number of the institution.
	 */
	private String phone;

	/*
	 * The fax machine number of the institution.
	 */
	private String fax;

	/*
	 * The website address of the institution.
	 */
	private String website;

	/*
	 * The name of a contact person at the institution.
	 */
	private String contactName;

	/*
	 * The telephone number of the contact person at the institution.
	 */
	private String contactPhone;

	/*
	 * The email address of the contact person at the institution.
	 */
	private String contactEmail;

	/*
	 * Any other notes about the institution.
	 */
	private String notes;

	/*
	 * 
	 */
	private Set<Account> accounts;

	/*
	 * A Set of String that contains all of the names used by instances of this class.
	 */
	private static Set<String> institutionNames = new HashSet<String>();

	/**
	 * Constructs a new instance using the default value for all of the fields.
	 */
	public Institution() {
		this.accounts = new TreeSet<Account>();
	}

	/**
	 * Returns the value of the {@code name} for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code name} field as a {@link String} object.
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Assigns the provided name in the {@code name} parameter as the value of the {@code name} field for this instance.
	 * The provided value cannot be assigned to any other instance, otherwise a {@link DuplicateNameException} object
	 * will be thrown. Also, it cannot be {@code null}, otherwise a {@link NullPointerException} object will be thrown.
	 * 
	 * @param name
	 *            A {@link String} object to assign as the value of the {@code name} field.
	 * 
	 * @throws DuplicateNameException
	 *             Thrown if the provided value is already assigned to another instance.
	 * @throws NullPointerException
	 *             Thrown if the provided value is {@code null}.
	 */
	public void setName(String name) throws DuplicateNameException, NullPointerException {
		/*
		 * Deal with a null value.
		 */
		if (null == name) {
			throw new NullPointerException();
		}

		/*
		 * Deal with the trivial case of setting the name to the current value.
		 */
		if (name.equals(this.name)) {
			return;
		}

		synchronized (Institution.institutionNames) {
			/*
			 * Check if the new name is already used.
			 */
			if (Institution.institutionNames.contains(name)) {
				throw new DuplicateNameException(Institution.class, name);
			}

			/*
			 * Otherwise, remove the old name, and add the new name.
			 */
			if (null != this.name) {
				Institution.institutionNames.remove(this.name);
			}
			Institution.institutionNames.add(name);
			this.name = name;
		}
	}

	/**
	 * Returns the value of the {@code address} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code address} field as a {@link String} object.
	 */
	public String getAddress() {
		return this.address;
	}

	/**
	 * Assigns the provided address in the {@code address} parameter as the value of the {@code address} field for this
	 * instance. There are no constraints on this value.
	 * 
	 * @param address
	 *            A {@link String} object to assign as the value of the {@code address} field.
	 */
	public void setAddress(String address) {
		this.address = address;
	}

	/**
	 * Returns the value of the {@code phone} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code phone} field as a {@link String} object.
	 */
	public String getPhone() {
		return this.phone;
	}

	/**
	 * Assigns the provided phone number in the {@code phone} parameter as the value of the {@code phone} field for this
	 * instance. There are no constraints on this value.
	 * 
	 * @param phone
	 *            A {@link String} object to assign as the value of the {@code phone} field.
	 */
	public void setPhone(String phone) {
		this.phone = phone;
	}

	/**
	 * 
	 * Returns the value of the {@code fax} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code fax} field as a {@link String} object.
	 */
	public String getFax() {
		return this.fax;
	}

	/**
	 * Assigns the provided fax number in the {@code fax} parameter as the value of the {@code fax} field for this
	 * instance. There are no constraints on this value.
	 * 
	 * @param fax
	 *            A {@link String} object to assign as the value of the {@code fax} field.
	 */
	public void setFax(String fax) {
		this.fax = fax;
	}

	/**
	 * Returns the value of the {@code website} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code website} field as a {@link String} object.
	 */
	public String getWebsite() {
		return this.website;
	}

	/**
	 * Assigns the provided web site in the {@code website} parameter as the value of the {@code website} field for this
	 * instance. There are no constraints on this value.
	 * 
	 * @param website
	 *            A {@link String} object to assign as the value of the {@code website} field.
	 */
	public void setWebsite(String website) {
		this.website = website;
	}

	/**
	 * Returns the value of the {@code contactName} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code contactName} field as a {@link String} object.
	 */
	public String getContactName() {
		return this.contactName;
	}

	/**
	 * Assigns the provided name in the {@code contactName} parameter as the value of the {@code contactName} field for
	 * this instance. There are no constraints on this value.
	 * 
	 * @param contactName
	 *            A {@link String} object to assign as the value of the {@code contactName} field.
	 */
	public void setContactName(String contactName) {
		this.contactName = contactName;
	}

	/**
	 * Returns the value of the {@code contactPhone} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code contactPhone} field as a {@link String} object.
	 */
	public String getContactPhone() {
		return this.contactPhone;
	}

	/**
	 * Assigns the provided phone number in the {@code contactPhone} parameter as the value of the {@code contactPhone}
	 * field for this instance. There are no constraints on this value.
	 * 
	 * @param contactPhone
	 *            A {@link String} object to assign as the value of the {@code contactPhone} field.
	 */
	public void setContactPhone(String contactPhone) {
		this.contactPhone = contactPhone;
	}

	/**
	 * Returns the value of the {@code contactEmail} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code contactEmail} field as a {@link String} object.
	 */
	public String getContactEmail() {
		return this.contactEmail;
	}

	/**
	 * Assigns the provided email in the {@code contactEmail} parameter as the value of the {@code contactEmail} field
	 * for this instance. There are no constraints on this value.
	 * 
	 * @param contactEmail
	 *            A {@link String} object to assign as the value of the {@code contactEmail} field.
	 */
	public void setContactEmail(String contactEmail) {
		this.contactEmail = contactEmail;
	}

	/**
	 * Returns the value of the {@code notes} field for this {@code Institution} instance.
	 * 
	 * @return the value of the {@code notes} field as a {@link String} object.
	 */
	public String getNotes() {
		return this.notes;
	}

	/**
	 * Assigns the provided notes in the {@code notes} parameter as the value of the {@code notes} field for this
	 * instance. There are no constraints on this value.
	 * 
	 * @param notes
	 *            A {@link String} object to assign as the value of the {@code notes} field.
	 */
	public void setNotes(String notes) {
		this.notes = notes;
	}

	/*
	 * Adds the Institution-Account side of the bidirectional one-to-many relationship between Accounts and
	 * Institutions. This method is invoked by the setInstitution() method of the Account class.
	 */
	void addAccount(Account account) {
		synchronized (this.accounts) {
			this.accounts.add(account);
		}
	}

	/*
	 * Removes the Institution-Account side of the bidirectional one-to-many relationship between Accounts and
	 * Institutions. This method is invoked by the setInstitution() method of the Account class.
	 */
	void removeAccount(Account account) {
		synchronized (this.accounts) {
			this.accounts.remove(account);
		}
	}

	/**
	 * Returns a {@link Collection} of {@link Account} objects that represent the accounts held at the institution
	 * represented by this instance.
	 * 
	 * @return A {@link Collection} of {@link Account} objects.
	 */
	public Collection<Account> getAccounts() {
		return new ArrayList<Account>(this.accounts);
	}

	/**
	 * 
	 */
	public void delete() {
		if (null != this.name) {
			Institution.institutionNames.remove(this.name);
			this.name = null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		} else if (null != obj && obj.getClass() == Institution.class) {
			return (null == this.name ? null == ((Institution) obj).name : this.name.equals(((Institution) obj).name));
		}

		return super.equals(obj);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return null == this.name ? 0 : this.name.hashCode();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Institution o) {
		if (null == this.name) {
			return null == o.name ? 0 : -1;
		} else if (null == o.name) {
			return 1;
		}

		return this.name.compareTo(o.name);
	}
}
